package com.cloudfun.campusshare.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.cloudfun.campusshare.common.constant.*;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.AuditDTO;
import com.cloudfun.campusshare.common.model.vo.ApplicationSearchVO;
import com.cloudfun.campusshare.common.model.vo.ApplicationVO;
import com.cloudfun.campusshare.common.model.vo.AttachVO;
import com.cloudfun.campusshare.common.model.vo.AuditVO;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.entity.*;
import com.cloudfun.campusshare.repo.*;
import com.cloudfun.campusshare.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Created by Huqin on 2019/12/30
 */
@Slf4j
@Service
public class ApplicationServiceImpl implements ApplicationService {

    @Autowired
    private ApplicationEntityRepo applicationEntityRepo;

    @Autowired
    private SysDictService sysDictService;

    @Autowired
    private AttachmentService attachmentService;

    @Autowired
    private AuditService auditService;

    @Autowired
    private MessageService messageService;

    @Autowired
    private AppEntityRepo appEntityRepo;

    @Autowired
    private UserEntityRepo userEntityRepo;

    @Autowired
    private AppService appService;

    @Autowired
    private AuthOrgEntityRepo authOrgEntityRepo;

    @Autowired
    private LoanEntityRepo loanEntityRepo;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public ApplicationEntity create(ApplicationType type, String bizId, List<String> contentParams) {
        String adminId = userRoleService.getAnyAdminUserId();
        return this.create(type, bizId, contentParams, adminId);
    }

    @Override
    public ApplicationEntity create(ApplicationType type, String bizId, List<String> contentParams, String auditorId) {
        SystemDictionaryEntity sysDict = sysDictService.getByTypeAndKey(SysDictType.APP_CONTENT_TEMPLATE, type.toString());
        String content;
        if (null == sysDict || StringUtils.isBlank(sysDict.getValue()) || CollectionUtils.isEmpty(contentParams)) {
            content = null;
        } else {
            content = sysDict.getValue();
            for (int i = 0; i < contentParams.size(); i++) {
                String placeHolder = "#{" + i + "}";
                content = content.replace(placeHolder, contentParams.get(i));
            }
        }
        // 如果该bizId对应的申请类型已经有申请
        ApplicationEntity oldApplication = applicationEntityRepo.findByBizIdAndTypeAndEnabled(bizId, type, true);
        if (null != oldApplication) {
            // 旧申请还未处理或者处理了但通过了 返回异常
            if (oldApplication.getStatus().equals(ApplicationStatus.UNPROCESSED)
                    || oldApplication.getResult().equals(ApplicationResult.PASSED)) {
                throw new BusinessException(Code.DUPLICATE_APPLICATION);
            }
            oldApplication.setStatus(ApplicationStatus.UNPROCESSED);
            oldApplication.setApplicantId(AuthenticationHolder.getUserId());
            oldApplication.setContent(content);
            oldApplication.setAuditorId(auditorId);
            oldApplication.setApplyTime(String.valueOf(System.currentTimeMillis()));
            return applicationEntityRepo.save(oldApplication);
        }
        // 新申请
        ApplicationEntity applicationEntity = ApplicationEntity.builder()
                .type(type)
                .bizId(bizId)
                .applicantId(AuthenticationHolder.getUserId())
                .content(content)
                .status(ApplicationStatus.UNPROCESSED)
                .enabled(true)
                .applyTime(String.valueOf(System.currentTimeMillis()))
                .auditorId(auditorId)
                .build();
        return applicationEntityRepo.save(applicationEntity);
    }

    @Override
    public void create(ApplicationType type, String bizId, List<String> contentParams, List<String> attachIds) {
        String adminId = userRoleService.getAnyAdminUserId();
        this.create(type, bizId, contentParams, attachIds, adminId);
    }

    @Override
    @Transactional
    public void create(ApplicationType type, String bizId, List<String> contentParams, List<String> attachIds, String auditorId) {
        ApplicationEntity applicationEntity = this.create(type, bizId, contentParams, auditorId);
        // copy attachment
        attachmentService.copyAttachment(attachIds, applicationEntity.getId());
    }

    @Override
    public ApplicationVO applicationDetail(String id) {
        Optional<ApplicationEntity> entityOptional = applicationEntityRepo.findById(id);
        if (!entityOptional.isPresent()) {
            return null;
        }
        if (!AuthenticationHolder.getUserId().equals(entityOptional.get().getAuditorId())) {
            return null;
        }
        return this.toVO(entityOptional.get());
    }

    private ApplicationVO toVO(ApplicationEntity applicationEntity) {
        if (null == applicationEntity) {
            return null;
        }
        ApplicationVO applicationVO = new ApplicationVO();
        BeanUtil.copyProperties(applicationEntity, applicationVO);
        applicationVO.setApplicantId(applicationEntity.getApplicant().getId());
        applicationVO.setApplicantName(applicationEntity.getApplicant().getUsername());
        // 附件信息 如果是企业认证 则返回
        if (applicationVO.getType().equals(ApplicationType.USER_ORG_AUTH)) {
            List<AttachVO> attachVOList = attachmentService.getAttachment(applicationVO.getBizId());
            applicationVO.setAttachVOList(attachVOList);
        }
        // 审核信息
        List<AuditVO> auditVOList = auditService.getByApplicationId(applicationVO.getId());
        if (CollectionUtils.isNotEmpty(auditVOList)) {
            AuditVO last = auditVOList.get(auditVOList.size() - 1);
            applicationVO.setRemark(last.getRemark());
        }
        // 其他信息
        if (ApplicationType.APP_CREATE.equals(applicationEntity.getType())) {
            AppEntity appEntity = appEntityRepo.findById(applicationEntity.getBizId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            applicationVO.setDeviceQuantityLimit(appEntity.getDeviceQuantityLimit());
        }
        applicationVO.setAuditVoList(auditVOList);
        return applicationVO;
    }

    @Override
    public ApplicationVO getValidApplicationDetailByBizTypeId(ApplicationType type, String bizId) {
        ApplicationEntity applicationEntity = applicationEntityRepo.findByBizIdAndTypeAndEnabled(bizId, type, true);
        return this.toVO(applicationEntity);
    }

    @Override
    @Transactional
    public void audit(AuditDTO auditDTO) {
        ApplicationEntity applicationEntity = applicationEntityRepo.findByIdAndEnabled(auditDTO.getApplicationId(), true);
        if (null == applicationEntity) {
            throw new BusinessException(Code.NODATA);
        }
        // 如果是APP创建，检查其设备数量限制
        if (applicationEntity.getType() == ApplicationType.APP_CREATE) {
            if (null == auditDTO.getDeviceQuantityLimit() || auditDTO.getDeviceQuantityLimit() < NumberUtils.LONG_ZERO) {
                throw new BusinessException(Code.PARAMETER_ERROR, "设备数量限制错误");
            }
        }
        if (!AuthenticationHolder.getUserId().equals(applicationEntity.getAuditorId())) {
            throw new BusinessException(Code.NODATA);
        }
        ApplicationStatus status = applicationEntity.getStatus();
        if (!ApplicationStatus.UNPROCESSED.equals(status)) {
            // 如果状态是已审核 则错误
            throw new BusinessException(Code.SYSTEM_ERROR);
        }
        // 创建审核信息
        AuditEntity auditEntity = auditService.create(auditDTO);
        // 复制审核结果到申请中
        applicationEntity.setResult(ApplicationResult.valueOf(auditDTO.getAuditResult().toString()));
        applicationEntity.setStatus(ApplicationStatus.PROCESSED);
        // 生成消息
        List<String> params = this.getContentParams(applicationEntity.getType(), applicationEntity.getBizId(), auditEntity);
        messageService.create(applicationEntity.getBizId(), applicationEntity.getAuditorId(), applicationEntity.getApplicant().getId(),
                MsgType.valueOf(applicationEntity.getType().toString() + "_AUDIT_RESULT"), Arrays.asList(auditDTO.getAuditResult().getName()), params);
        // 修改具体的业务信息
        this.updateBizStatus(applicationEntity.getType(), applicationEntity.getBizId(), applicationEntity.getResult(), auditDTO);
    }

    /**
     * 更新业务状态
     *
     * @param type
     * @param bizId
     * @param result
     */
    private void updateBizStatus(ApplicationType type, String bizId, ApplicationResult result, AuditDTO auditDTO) {
        if (ApplicationType.APP_CREATE.equals(type)) {
            AppEntity appEntity = appEntityRepo.findById(bizId).orElseThrow(() -> new BusinessException(Code.NODATA));
            AppStatus appStatus = ApplicationResult.FAILED.equals(result) ? AppStatus.CREATE_APPLY_FAILED : AppStatus.CREATE_APPLY_SUCCESS;
            // 设置应用绑定设备限制数量
            appEntity.setDeviceQuantityLimit(auditDTO.getDeviceQuantityLimit());
            appEntity.setStatus(appStatus.getCode());
            appEntityRepo.save(appEntity);
            return;
        }
        if (ApplicationType.APP_TO_PROD.equals(type)) {
            AppEntity appEntity = appEntityRepo.findById(bizId).orElse(null);
            AppStatus appStatus = ApplicationResult.FAILED.equals(result) ? AppStatus.TO_PROD_APPLY_FAILED : AppStatus.TO_PROD_APPLY_SUCCESS;
            if (AppStatus.TO_PROD_APPLY_SUCCESS.equals(appStatus)) {
                appService.onToProdSuccess(appEntity);
            }
            appEntity.setStatus(appStatus.getCode());
            appEntityRepo.save(appEntity);
            return;
        }
        if (ApplicationType.USER_ORG_AUTH.equals(type)) {
            AuthOrgEntity authOrgEntity = authOrgEntityRepo.findById(bizId).orElse(null);
            authOrgEntity.setResult(result);
            UserEntity userEntity = userEntityRepo.findById(authOrgEntity.getUserId()).orElse(null);
            AuthStatus authStatus = ApplicationResult.FAILED.equals(result) ? AuthStatus.AUTH_FAILURE : AuthStatus.AUTH_SUCCESS;
            // 如果通过了且商户的企业名为空 则设置商户企业名
            if (ApplicationResult.PASSED == result && StringUtils.isBlank(userEntity.getEnterpriseName())) {
                userEntity.setEnterpriseName(authOrgEntity.getOrgName());
            }
            userEntity.setAuthStatus(authStatus.getCode());
            userEntityRepo.save(userEntity);
            authOrgEntityRepo.save(authOrgEntity);
            return;
        }
        if (ApplicationType.LOAN.equals(type)) {
            LoanEntity loanEntity = loanEntityRepo.findById(bizId).orElse(null);
            LoanStatus loanStatus = ApplicationResult.FAILED.equals(result) ? LoanStatus.FAIL : LoanStatus.WAIT_MAKE_LOAN;
            loanEntity.setStatus(loanStatus);
            loanEntityRepo.save(loanEntity);
        }
        if (ApplicationType.ISSUE_LOAN.equals(type)) {
            LoanEntity loanEntity = loanEntityRepo.findById(bizId).orElse(null);
            LoanStatus loanStatus = LoanStatus.FLOW_END;
            loanEntity.setStatus(loanStatus);
            loanEntity.setConfirmTime(String.valueOf(System.currentTimeMillis()));
            loanEntityRepo.save(loanEntity);
        }
    }

    /**
     * 获取参数
     *
     * @param applicationType
     * @param bizId
     * @param auditEntity
     * @return
     */
    private List<String> getContentParams(ApplicationType applicationType, String bizId, AuditEntity auditEntity) {
        List<String> params = new ArrayList<>();
        switch (applicationType) {
            case APP_TO_PROD:
            case APP_CREATE:
                AppEntity appEntity = appEntityRepo.findById(bizId).orElse(null);
                params.add(appEntity.getName());
                params.add(appEntity.getDescription());
                break;
            case USER_ORG_AUTH:
                break;

        }
        String remark = StringUtils.isBlank(auditEntity.getRemark()) ? "-" : auditEntity.getRemark();
        params.add(remark);
        return params;
    }

    /**
     * 更新是否有效字段
     *
     * @param applicationEntities
     * @param enabled
     * @return
     */
    private Map<String, String> updateEnabled(List<ApplicationEntity> applicationEntities, boolean enabled) {
        // 更新为指定状态
        Map<String, String> res = new HashMap<>(applicationEntities.size());
        List<ApplicationEntity> needUpdateEntities = applicationEntities.stream().filter(applicationEntity -> {
            if (!AuthenticationHolder.getUserId().equals(applicationEntity.getAuditorId())) {
                res.put(applicationEntity.getId(), "找不到对应数据");
                return false;
            }
            if (!enabled) {
                // 未处理的不允许删除
                if (ApplicationStatus.UNPROCESSED.equals(applicationEntity.getStatus())) {
                    res.put(applicationEntity.getId(), "审核未处理，不能删除");
                    return false;
                }
            }
            applicationEntity.setEnabled(false);
            return true;
        }).collect(Collectors.toList());

        applicationEntityRepo.saveAll(needUpdateEntities);
        return res;
    }

    @Override
    @Transactional
    public Map<String, String> updateEnabled(List<String> ids) {
        List<ApplicationEntity> applicationEntities = applicationEntityRepo.findAllByIdInAndEnabled(ids, true);
        Set<String> idSet = new HashSet<>(ids);
        Map<String, String> res = applicationEntities.stream()
                .map(ApplicationEntity::getId)
                .filter(id -> !idSet.contains(id))
                .collect(Collectors.toMap(Function.identity(), (o) -> "找不到对应数据"));
        res.putAll(this.updateEnabled(applicationEntities, false));
        return res;
    }

    @Override
    @Transactional
    public Map<String, String> updateEnabled(ApplicationSearchVO searchVO) {
        Specification<ApplicationEntity> spec = this.getSpec(searchVO);
        List<ApplicationEntity> applicationEntities = applicationEntityRepo.findAll(spec);
        return this.updateEnabled(applicationEntities, false);
    }

    @Override
    @Transactional
    public Map<String, String> updateEnabled() {
        return this.updateEnabled(new ApplicationSearchVO());
    }

    @Override
    public PageResult<ApplicationVO> search(ApplicationSearchVO searchVO, Pageable pageable) {
        Specification<ApplicationEntity> specification = getSpec(searchVO);
        Page<ApplicationEntity> entityPage = applicationEntityRepo.findAll(specification, pageable);
        long totalElements = entityPage.getTotalElements();
        List<ApplicationVO> content = entityPage.getContent().stream()
                .map(applicationEntity -> {
                    ApplicationVO vo = new ApplicationVO();
                    BeanUtil.copyProperties(applicationEntity, vo);
                    vo.setApplicantId(applicationEntity.getApplicant().getId());
                    vo.setApplicantName(applicationEntity.getApplicant().getUsername());
                    return vo;
                }).collect(Collectors.toList());

        PageResult<ApplicationVO> result = new PageResult<>();
        result.setElements(content);
        result.setTotalSize(totalElements);
        return result;
    }

    private Specification<ApplicationEntity> getSpec(ApplicationSearchVO searchVO) {
        return (root, query, cb) -> {
            List<Predicate> predicateList = new ArrayList<>();
            if (null != searchVO.getType()) {
                predicateList.add(cb.equal(root.get("type"), searchVO.getType()));
            }
            if (null != searchVO.getStatus()) {
                predicateList.add(cb.equal(root.get("status"), searchVO.getStatus()));
            }
            if (null != searchVO.getResult()) {
                predicateList.add(cb.equal(root.get("result"), searchVO.getResult()));
            }
            predicateList.add(cb.equal(root.get("enabled"), true));
            predicateList.add(cb.equal(root.get("auditorId"), AuthenticationHolder.getUserId()));
            if (StringUtils.isNotBlank(searchVO.getValue())) {
                List<Predicate> like = new ArrayList<>();
                like.add(cb.like(root.get("content"), "%" + searchVO.getValue() + "%"));
                //两张表关联查询
                Join<ApplicationEntity, UserEntity> userJoin = root.join(root.getModel().getSingularAttribute("applicant", UserEntity.class), JoinType.LEFT);
                like.add(cb.like(userJoin.get("username"), "%" + searchVO.getValue() + "%"));
                Predicate[] predicates = new Predicate[like.size()];
                predicateList.add(cb.or(like.toArray(predicates)));
            }
            Predicate[] predicates = new Predicate[predicateList.size()];
            predicateList.toArray(predicates);
            return cb.and(predicates);
        };
    }

    @Override
    @Transactional
    public void deleteByBizId(String bizId) {
        List<ApplicationEntity> allByBizId = applicationEntityRepo.findAllByBizId(bizId);
        // 先删除相关的审核
        allByBizId.forEach(applicationEntity -> {
            if (!AuthenticationHolder.getUserId().equals(applicationEntity.getAuditorId())) {
                return;
            }
            auditService.deleteByApplicationId(applicationEntity.getId());
        });
        applicationEntityRepo.deleteAll(allByBizId);
    }

    @Override
    public ApplicationEntity getLatestValid(String bizId, ApplicationType type) {
        return applicationEntityRepo.findTopByBizIdAndTypeAndEnabledOrderByCreateTimeDesc(bizId, type, true);
    }

    @Override
    public AuditEntity getLatestAuditByBizIdAndType(String bizId, ApplicationType type) {
        ApplicationEntity applicationEntity = this.getLatestValid(bizId, type);
        if (null == applicationEntity) {
            return null;
        }
        if (!AuthenticationHolder.getUserId().equals(applicationEntity.getAuditorId())
                && !AuthenticationHolder.getUserId().equals(applicationEntity.getApplicantId())) {
            return null;
        }
        return auditService.getLatestByApplicationId(applicationEntity.getId());
    }

    @Override
    public Long countByTypeNotInAndStatusAndEnabled(Collection<ApplicationType> types, ApplicationStatus status) {
        return applicationEntityRepo.countByTypeNotInAndStatusAndEnabled(types, status, true);
    }
}
